package task;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created by lizhenyu on 2019/9/15
 * description 手写线程池
 */
public class MyThreadPool {

    // 线程池的实现需要下面几个条件：

    // 1、阻塞队列作为任务仓库
    private BlockingQueue<Runnable> queue;

    // 2、需要一个放置工作线程的集合
    private List<Thread> workers;

    // 3、工作线程的实现类，每个工作线程持有相同的线程池对象
    public static class Worker extends Thread{
        // 定义线程池变量
        private MyThreadPool pool;

        public Worker(MyThreadPool pool) {
            this.pool = pool;
        }

        // 去线程池的仓库拿任务
        @Override
        public void run() {
           while (this.pool.isWorking || this.pool.getTaskNum() > 0) {
               Runnable task = null;
               try {
                   if(this.pool.isWorking) {
                       // 线程池运行时拿任务，使用阻塞的方式
                       task = pool.queue.take();
                   } else {
                       // 线程池要关闭时拿任务，不能再阻塞了
                       task = pool.queue.poll();
                   }
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               if (task != null) {
                   task.run();    //执行任务
                   System.out.println("线程 " + Thread.currentThread().getName() + " 执行完毕");
               } else {
                   System.out.println("线程 " + Thread.currentThread().getName() + " 未取得任务");
               }
           }
        }
    }

    // 4、初始化仓库和工作线程的集合
    public MyThreadPool(int poolSize, int taskSize) {
        if(poolSize > 0 && taskSize > 0) {
            // 初始化阻塞队列和工作线程集合
            queue = new LinkedBlockingQueue<>(taskSize);
            workers = Collections.synchronizedList(new ArrayList<>(poolSize));  //创建线程安全的ArrayList
            System.out.println("初始化阻塞队列和工作线程");

            // 加入各个工作线程对象
            for (int i = 0; i < poolSize; i++) {
                Worker worker = new Worker(this);
                worker.start();   // 开启工作线程
                workers.add(worker);
            }
            System.out.println("工作线程加入完毕");
        } else {
            throw new IllegalThreadStateException("不合法线程池参数");
        }
    }

    // 5、定义向仓库放置任务的方法，非阻塞
    public boolean submit(Runnable task) {
        if(this.isWorking) {
            // offer函数用于向阻塞队列插入特殊值
            return this.queue.offer(task);
        }
        return false;
    }

    // 6、定义向仓库放置任务的方法，阻塞
    public void execute(Runnable task) {
        if(this.isWorking) {
            // put函数用于向阻塞队列插入阻塞对象
            try {
                this.queue.put(task);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 7、提供一个关闭线程池的方法，释放资源
    private volatile boolean isWorking = true;
    public void shutdown() {
        System.out.println("线程池将要关闭");

        // 通知工作线程，已经需要关闭线程池
        this.isWorking = false;

        // 将阻塞的工作线程全部中断
        for (Thread worker : workers) {
            // 判断工作线程状态是否阻塞
            if(worker.getState().equals(Thread.State.BLOCKED) || worker.getState().equals(Thread.State.WAITING) ) {
                worker.interrupt();
            }
        }

        System.out.println("线程池关闭完成");
    }

    // 8、其他自定义方法
    public int getTaskNum() {
        return this.queue.size();
    }

    public int getPoolSize() {
        return this.workers.size();
    }

    // 主函数测试线程池
    public static void main(String[] args) {
        // poolSize: 线程池的大小，即工作线程的最大个数
        // taskSize: 阻塞队列最多存放任务数
        MyThreadPool pool = new MyThreadPool(3, 6);
        // 依次放入6个任务，让线程池管理工作线程调度执行
        for(int i = 0; i < 6; i++) {
            final int k = i;
            if(pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("放入任务" + k);
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        System.out.println("任务" + k + "被中断");
                    }
                }
            })) {
            } else {
                System.out.println("放入任务失败");
            }
        }
        pool.shutdown();
    }
}
